%%--- coding:utf-8 ---
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% File Name: mc_worker_logic
%%% Created on : 2024/4/16 20:54
%%% @author Gaylen 252323463@qq.com
%%% @copyright (C) 2024, freedom
%%% @doc
%%%
%%% @end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

-module(mc_worker_logic).
-author("Gaylen").
-include("mongodb_driver.hrl").

%% API
-export([
    connect/1,
    ping/1,
    check_alive/1,

    process_request/4,
    unpack_buffer/2
]).

%% Make connection to database and return socket
-spec connect(proplists:proplist()) -> {ok, port()} | {error, inet:posix()}.
connect(Conf) ->
    Timeout = mc_utils:get_value(timeout, Conf, infinity),
    Host = mc_utils:get_value(host, Conf, "127.0.0.1"),
    Port = mc_utils:get_value(port, Conf, 27017),
    SslOpts = mc_utils:get_value(ssl_opts, Conf, []),
    SSL = mc_utils:get_value(ssl, Conf, false),
    do_connect(Host, Port, Timeout, SSL, SslOpts).

%% @private
do_connect(Host, Port, Timeout, true, Opts) ->
    {ok, _} = application:ensure_all_started(ssl),
    ssl:connect(Host, Port, [binary, {active, true}, {packet, raw}] ++ Opts, Timeout);
do_connect(Host, Port, Timeout, false, _) ->
    gen_tcp:connect(Host, Port, [binary, {active, true}, {packet, raw}], Timeout).

ping(State) ->
    RequestId = mc_utils:request_id(),
    OpBin = mongo_protocol:encode_ping(RequestId, <<"admin">>),
    process_request(RequestId, OpBin, self(), State, ?MONGO_REQ_PING).

check_alive(State) ->
    #mongo_worker_state{
        alive_tick = AliveTick
    } = State,
    NowTick = erlang:system_time(seconds),
    if
        AliveTick + 120 =< NowTick ->
            %% 心跳超时
            mc_worker:disconnect(self()),
            State;
        true ->
            State
    end.

process_request(RequestId, OpBin, From, State) ->
    process_request(RequestId, OpBin, From, State, ?MONGO_REQ_ASYNC).
process_request(RequestId, OpBin, From, State, ReqType) ->
    #mongo_worker_state{
        socket = Socket,
        net_module = NetModule,
        request_storage = RequestStorageMaps
    } = State,
    ok = NetModule:send(Socket, OpBin),
    ReqStorageRec = #mongo_reqstorage{
        request_id = RequestId,
        req_from = From,
        req_type = ReqType
    },
    NewRequestStorageMaps = maps:put(RequestId, ReqStorageRec, RequestStorageMaps),
    State#mongo_worker_state{
        request_storage = NewRequestStorageMaps
    }.

unpack_buffer(<<Length:32/signed-little, Buffer/binary>>, State) when byte_size(Buffer) >= (Length - 4) ->
    PayloadLength = Length - 4,
    <<PayLoadBin:PayloadLength/binary, RestBuffer/binary>> = Buffer,
    <<_RequestId:32/signed-little, ResponseTo:32/signed-little, _RestPayLoadBin/binary>> = PayLoadBin,
    RequestStorageMaps = State#mongo_worker_state.request_storage,
    NewState =
        case maps:get(ResponseTo, RequestStorageMaps, undefined) of
            #mongo_reqstorage{ req_from = ReqFrom, req_type = ReqType } ->
                if
                    ReqType =:= ?MONGO_REQ_PING ->
                        NewState0 = State#mongo_worker_state{
                            request_storage = maps:remove(ResponseTo, RequestStorageMaps)
                        },
                        priv_ping_reply(PayLoadBin, NewState0);
%%                    ReqType =:= ?MONGO_REQ_ISMASTER ->
%%                        NewState0 = State#mongo_worker_state{
%%                            request_storage = maps:remove(ResponseTo, RequestStorageMaps)
%%                        },
%%                        priv_is_master_reply(PayLoadBin, NewState0);
                    true ->
                        priv_reply(ReqFrom, PayLoadBin),
                        State#mongo_worker_state{
                            request_storage = maps:remove(ResponseTo, RequestStorageMaps)
                        }
                end;
            _ ->
                %% TODO 输出日志提示没有找到要回复的进程
                State
        end,
    unpack_buffer(RestBuffer, NewState);
unpack_buffer(RestBuffer, State) ->
    State#mongo_worker_state{
        buffer = RestBuffer
    }.

priv_reply(undefined, _PayLoadBin) ->
    ok;
priv_reply(ReqFrom, PayLoadBin) ->
    gen_server:reply(ReqFrom, PayLoadBin).

priv_ping_reply(PayLoadBin, State) ->
    {OpMsgRec, _Bin} = mongo_protocol:decode_reply(PayLoadBin),
    IsOk = erlang:abs(maps:get(<<"ok">>, OpMsgRec#mongo_opmsg.document, 0) - 1.0),
    if
        IsOk < 0.0000001 ->
            NewState = State#mongo_worker_state{
                alive_tick = erlang:system_time(seconds)
            },
            NewState;
%%            priv_is_master(NewState);
        true ->
            %% PING返回失败，不存活
            State#mongo_worker_state{
                alive_tick = 0
            }
    end.

%%priv_is_master(State) ->
%%    RequestId = mc_utils:request_id(),
%%    OpBin = mongo_protocol:encode_is_master(RequestId, <<"admin">>),
%%    process_request(RequestId, OpBin, self(), State, ?MONGO_REQ_ISMASTER).

%%priv_is_master_reply(PayLoadBin, State) ->
%%    {OpMsgRec, _Bin} = mongo_protocol:decode_reply(PayLoadBin),
%%    IsOk = erlang:abs(maps:get(<<"ok">>, OpMsgRec#mongo_opmsg.document, 0) - 1.0),
%%    if
%%        IsOk < 0.0000001 ->
%%            IsMaster = maps:get(<<"ismaster">>, OpMsgRec#mongo_opmsg.document, false),
%%            State#mongo_worker_state{
%%                is_master = IsMaster
%%            };
%%        true ->
%%            %% PING返回失败，不存活
%%            State#mongo_worker_state{
%%                is_master = false
%%            }
%%    end.
